// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
//
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
//
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
//
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eva.epc.common.util.CommonUtils;
import com.eva.epc.common.util.RestHashMap;
import com.eva.framework.HttpController;
import com.eva.framework.Processor;
import com.eva.framework.Processor.User;
import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.dto.DataFromClient;
import com.eva.framework.dto.DataFromServer;
import com.eva.framework.dto.LoginInfo2;
import com.eva.framework.dto.LogoutInfo;
import com.eva.framework.utils.EndsExceptionFactory;
import com.eva.framework.utils.LoggerFactory;
import com.google.gson.Gson;
import com.x52im.rainbowchat.cache.UsersInfoCacheProvider;
import com.x52im.rainbowchat.common.dto.AutoUpdateInfoFromServer;
import com.x52im.rainbowchat.common.dto.cnst.EncodeConf;
import com.x52im.rainbowchat.common.dto.cnst.MyProcessorConst;
import com.x52im.rainbowchat.common.dto.cnst.OperateLog;
import com.x52im.rainbowchat.exception.TokenException;
import com.x52im.rainbowchat.http.logic.*;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rainbowchat.http.util.JedisTools;

/**
 * RainbowChat的服务端HTTP REST接口服务的MVC核心控制器实现类（用于APP等通过HTTP POST方式）.
 * <p>
 * <b>说明：</b>本子类只需实现用户级的业务逻辑.
 * <p>
 * <b>调用：</b>HTTP的POST方式。<br>
 * <b>用途：</b>本类主要用于实现APP端的http rest接口调用，服务端返回格式为JSON。
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @since 3.0
 */
public class MyControllerJSON extends HttpController {
    private static final long serialVersionUID = 1L;

    /**
     * 用于执行客户端登陆请求时的客户端程序版本检查的辅助对象
     */
    private ClientAPKVersionChecker clientAPKVersionChecker = null;

    private GiftProcessor2 giftProcessor = new GiftProcessor2();
    private LogicProcessor2 logicProcessor = new LogicProcessor2();
    private IMAdminProcessor adminProcessor = new IMAdminProcessor();
    private FileInfoProcessor fileInfoProcessor = new FileInfoProcessor();
    private GroupChatProcessor groupChatProcessor = new GroupChatProcessor();
    private MoneyProcessor moneyProcessor = new MoneyProcessor();
    private AlipayProcessor alipayProcessor = new AlipayProcessor();
    private AlipayCertProcessor alipayCertProcessor = new AlipayCertProcessor();

    /**
     * <p>
     * MVC框架核心调度处理方法.
     * <p>
     * RainbowChat的所有http rest数据接口请求，将由本方法捕获，并进行业务调度和处理。
     *
     * @param fc  封装了客户端发过来的相关数据（本方法中是必须的参数）
     * @param req 客户端请求实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @param res 客户端响应实例引用（把它传过来完全是未了考虑以后可能有其它作用，本方法中它不是必须的）
     * @return 处理完返回的对象（要给客户端的）
     * @throws Exception 本方法调用中出现任何异常时都会抛出，方法将中断执行
     * @see #switchUserProcessor(DataFromClient, HttpServletRequest, HttpServletResponse, User)
     */
    @Override
    protected Object dispatch(DataFromClient dfc, HttpServletRequest req, HttpServletResponse res) throws Exception {
        Object retObj = null;
        int processor_id = dfc.getProcessorId();

        LoggerFactory.getLog().debug("〖 [HTTP][客户端类型:" + printOS(dfc.getDevice()) + "] 捕获REST【接口"
                + printRestNum(processor_id, dfc.getJobDispatchId(), dfc.getActionId())
                + "】的请求，安全token=" + dfc.getToken() + " 〗");

        // Rest框架需要首先对客户端发送过来的数据进行类型检查，防止Android、标准Java这种java系
        // 的平台发过来的是除String之外的平台发过来的是除String之外的Java序列化对象而非JSON文本
        if ((dfc.getNewData() != null && !(dfc.getNewData() instanceof String))
                || (dfc.getOldData() != null && !(dfc.getOldData() instanceof String))) {
            String ds = "[HTTP]Rest框架要求客户端发过来的数据必须是JSON文本！而现在拿到的是：" +
                    "newData=" + (dfc.getNewData() != null ? dfc.getNewData().getClass().getName() : "")
                    + ", oldData=" + (dfc.getOldData() != null ? dfc.getOldData().getClass().getName() : "");
            LoggerFactory.getLog().error(ds);

            // TODO: 上线时需删除
            if (dfc.getNewData() != null && !(dfc.getNewData() instanceof String)) {
                dfc.setNewData(JSONObject.toJSONString(dfc.getNewData()));
            }
            if (dfc.getOldData() != null && !(dfc.getOldData() instanceof String)) {
                dfc.setOldData(JSONObject.toJSONString(dfc.getOldData()));
            }

//            throw new RuntimeException(ds);
        }

        Processor processor = null;

        // 【接口1009】登陆认证和版本检查综合接口（Android专用）
        if (processor_id == MyProcessorConst.PROCESSOR_LOGIN$CHECKUPDATE_4A) {
            LoginInfo2 ai = JSON.parseObject((String) dfc.getNewData(), LoginInfo2.class);
            String uidOrmail = ai.getLoginName();
            String clientCurrentVersion = ai.getClientVersion();
            String oprContent = ai.getDeviceInfo();
            LoggerFactory.getLog().debug("[HTTP]<版本检查START> ..................用户：" + uidOrmail);

            //** 开始进行版本检查
            AutoUpdateInfoFromServer aui = getClientAPKVersionChecker().checkClientAPKVersion(clientCurrentVersion);
            //客户端需要更新
            if (aui.isNeedUpdate() && !("-1".equals(ai.getClientVersion()))) {
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_NEED_UPDATE, uidOrmail, oprContent);
                LogicUtils.addOperateLog(oprLog);
                LoggerFactory.getLog().debug("[HTTP]<版本检查END> " + uidOrmail + "需首先更新程序版本("
                        + "手机当前版本：" + clientCurrentVersion + "，需要更新到版本：" + aui.getLatestClientAPKVercionCode()
                        + ")，更新信息将立即返回给客户端，RETURN.");
//				return aui;
            }

            //** 登陆验证
            LoggerFactory.getLog().debug("[HTTP]<登陆时：登陆验证START> " + uidOrmail + "，接下来将进入真正的登陆验证流程.");
            // 登陆认证：返回null则表示认证实现，否则返回的是登陆用户的个人信息
            RosterElementEntity authedUser = authrizationLogin(ai, req.getRemoteHost(), true);
            if (authedUser == null) {
                LoggerFactory.getLog().debug("[HTTP]<登陆时：登陆验证END> " + uidOrmail + "的账号无效或登陆密码不正确(在其它手机修改过？).");
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_FALIURE, uidOrmail, oprContent);
                LogicUtils.addOperateLog(oprLog);
            } else {
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_SUCCESS, uidOrmail, oprContent);
                LogicUtils.addOperateLog(oprLog);
            }

            RestHashMap retMap = RestHashMap.n().p("update_info", JSON.toJSONString(aui));
            if (authedUser != null) {
                retMap.p("authed_info", JSON.toJSONString(authedUser));
            }

            return JSON.toJSONString(retMap);
        }
        // 【接口1010】客户端版本更新检查接口（Android专用）
        else if (processor_id == MyProcessorConst.PROCESSOR_CHECK_UPDATE_4A) {
            AutoUpdateInfoFromServer aui = new AutoUpdateInfoFromServer();
            if (dfc.getNewData() != null) {
                String clientCurrentVersion = (String) dfc.getNewData();
                //开始进行版本检查
                aui = getClientAPKVersionChecker().checkClientAPKVersion(clientCurrentVersion);
            }

            return JSON.toJSONString(aui);
        }
        // 【接口1013】客户端登陆接口（Android\ios均可使用）
        else if (processor_id == MyProcessorConst.PROCESSOR_LOGIN_4ALL) {
            LoginInfo2 ai = JSON.parseObject((String) dfc.getNewData(), LoginInfo2.class);

            String oprContent = ai.getDeviceInfo();
            String uidOrmailOrPhone = null;
            if (!CommonUtils.isStringEmpty(ai.getLoginPhone(), true)) {
                uidOrmailOrPhone = ai.getLoginPhone();
            } else {
                String uidOrmail = ai.getLoginName();
            }

            //** 登陆验证
            LoggerFactory.getLog().debug("[HTTP]<登陆时：通用登陆验证START> " + uidOrmailOrPhone + "，接下来将进入真正的登陆验证流程.");
            // 登陆认证：返回null则表示认证实现，否则返回的是登陆用户的个人信息
            RosterElementEntity authedUser = authrizationLogin(ai, req.getRemoteHost(), false);
            if (authedUser == null) {
                LoggerFactory.getLog().debug("[HTTP]<登陆时：通用登陆验证END> " + uidOrmailOrPhone + "的账号无效或登陆密码不正确(在其它手机修改过？).");
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_FALIURE, uidOrmailOrPhone, oprContent);
                LogicUtils.addOperateLog(oprLog);
            } else {
                // 记录操作日志
                OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGIN_SUCCESS, uidOrmailOrPhone, oprContent);
                LogicUtils.addOperateLog(oprLog);
            }

            //** 返回值
            return JSON.toJSONString(authedUser);
        }
        // 【接口-2】客户端注销登陆接口（Android\ios\web均可使用）
        else if (processor_id == MyProcessorConst.PROCESSSOR_LOGOUT) {
            LogoutInfo ao = JSON.parseObject((String) dfc.getNewData(), LogoutInfo.class);
            authrizationLogout(ao, req.getRemoteHost());
            return null;
        } else {
            // 出于安全考虑，此token可用于检查每一个http rest请求的合法性，检查策略可由业务逻辑层来考虑和处理，具体请开发者据自已的安全策略来定制
            boolean isPass = false;

            if (!CommonUtils.isStringEmpty(dfc.getNotoken())) {
                isPass = true;
            }

            if (!isPass) {
                String tokenInf = BaseConf.getInstance().getFILTER_TOKEN_INF();
                String[] infs = tokenInf.split("\\|");
                for (int i = 0; i < infs.length; i++) {
                    if (infs[i].equals(dfc.getInfName())) {
                        isPass = true;
                        break;
                    }
                }
            }

            if (!isPass) {
                isPass = LogicUtils.checkToken(dfc);
            }

            if (!isPass) {
                throw new TokenException("当前登录号码token校验失败");
            }

            switch (processor_id) {
                // 礼品相关的接口处理器
                case MyProcessorConst.PROCESSOR_GIFT:
                    processor = giftProcessor;
                    break;
                // IM的基本业务逻辑接口处理器
                case MyProcessorConst.PROCESSOR_LOGIC:
                    processor = logicProcessor;
                    break;
                // 专用于后台管理端的一些特权接口，以后代码可考虑独立出去，保证安全性
                case MyProcessorConst.PROCESSOR_ADMIN:
                    processor = adminProcessor;
                    break;
                // 大文件信息查询接口的处理器
                case MyProcessorConst.PROCESSOR_FILE:
                    processor = fileInfoProcessor;
                    break;
                // 群组聊天相关管理接口的处理器
                case MyProcessorConst.PROCESSOR_GROUP_CHAT:
                    processor = groupChatProcessor;
                    break;
                // MONEY相关管理接口的处理器
                case MyProcessorConst.MONEY:
                    processor = moneyProcessor;
                    break;
                // alipay相关
                case MyProcessorConst.ALIPAY_CERT:
                    processor = alipayCertProcessor;
                    break;
                // alipay相关
                case MyProcessorConst.ALIPAY:
                    processor = alipayProcessor;
                    break;
                //找不到处理器
                default:
                    throw EndsExceptionFactory.INVALID_PROCESSOR_ID(processor_id);
            }

            String basePath = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + req.getContextPath() + "/";
//			System.out.println("[HTTP]basePath = " +  basePath);
            // 借用一个User对象，仅仅是用来存放Ip地址而已
            User u = new User();
            u.setUserUid(dfc.getUserUid());
            u.setIp(req.getRemoteHost());
            if (processor != null) {
                if (processor instanceof LogicProcessor2) {
                    retObj = processor.process(dfc.getJobDispatchId()
                            , dfc.getActionId()
                            , dfc.getNewData()
                            , new Object[]{basePath, dfc.getOldData()}
                            , u);
                } else {

                    retObj = processor.process(dfc.getJobDispatchId()
                            , dfc.getActionId(), dfc.getNewData()
                            , dfc.getOldData(), u);
                }

                // 返回值类型检查
                if (retObj != null && !(retObj instanceof String)) {
                    String ds = "[HTTP]Rest框架中processor处理完成" +
                            "的返回值必须是JSON文本哦！(retObj=" + retObj.getClass().getName() + ")";
                    LoggerFactory.getLog().error(ds);
                    throw new RuntimeException(ds);
                }
            }

            return retObj;
        }
    }

    /**
     * 获取Android的版本检查对象。
     *
     * @return
     */
    private ClientAPKVersionChecker getClientAPKVersionChecker() {
        if (clientAPKVersionChecker == null) {
            clientAPKVersionChecker = new ClientAPKVersionChecker(this.getServletContext().getRealPath("/"));//项目的发布磁盘目录就是apk文件放置的目录（已含末尾“/“） //ClientAPKVersionChecker.getAPKPath());
        }
        return clientAPKVersionChecker;
    }

    /**
     * 验证登陆用户的人信息.
     *
     * @param ai 登陆信息封装对象
     * @return 登陆成功则返回此用户的完整个人信息，登陆不成功则返回null
     * @throws Exception
     */
    public static RosterElementEntity authrizationLogin(LoginInfo2 ai, String ip, boolean isAndroid) throws Exception {
        String uidOrMail = ai.getLoginName();
        String psw = ai.getLoginPsw();
        String phone = ai.getLoginPhone();

        String baseSql = null;
        if (!CommonUtils.isStringEmpty(ai.getLoginPhone())) {
            baseSql = LogicUtils.getUSER_INFI_BASE_SQL(null) + " where user_status = 1 and phone='" + ai.getLoginPhone() +
                    "' and user_psw='" + CommonUtils.encyptPassword(psw, "") + "'";
        } else {
            baseSql = LogicUtils.getUSER_INFI_BASE_SQL(null) + " where user_status = 1" +
                    "	and " + (CommonUtils.isEmail(uidOrMail) ? "user_mail='" + uidOrMail + "'" : "user_uid=" + uidOrMail) +
                    "	and user_psw='" + CommonUtils.encyptPassword(psw, "") + "'";
        }


        RosterElementEntity userAuthedInfo = LogicUtils.constructUserInfo(LogicProcessor2.db.querySingleRowItems(baseSql));

        if (userAuthedInfo != null) {
            //************************************** 【1】从iOS设备上登陆的特殊处理 ****************************************
            // ios设备的token，用于实现对ios设备的离线消息推送（详见苹果的APNs服务）
            String iosDeviceToken = null;
            // 如果是来自苹果手机登陆的特殊处理
            if (ai.isIOS()) {
                // 取出提交上来的ios device token字段（此字段目前只供ios设备使用）
                iosDeviceToken = ai.getDeviceID();
                LoggerFactory.getLog().debug("【iOS登陆特殊处理】当前账号" + uidOrMail + "是从iOS设备登陆的（deviceToken=" + iosDeviceToken + "） 》》》");
                if (!CommonUtils.isStringEmpty(iosDeviceToken, true)) {
                    // 以下代码逻辑用于查出此前在此ios设备上登陆过的用户，并设置这些账号的ios_device_token字段为空
                    //
                    //*【补充说明】：为什么要设置在此ios设备上登陆过的用户的ios_device_token字段为空？
                    //* - 根据ios的device token生成规则，原则上同一个APP在同一个手机上的token是相同的(某些情况下除外)，
                    //* - 理想情况下不同的用户账号在同一台手机上登陆过，他们提交到服务端的device token是一样的，
                    //* - 而苹果的APNs离线消息推送只能区分device token是哪个手机，但不能区分这个token是哪个手机上
                    //* - 的具体的那个用户，所以这么做是为了防止出现：登陆过别的账号后之前的用户消息还会被推过来、
                    //* - 以及不同的账号如果恰好在同一个群里的话，就有可能同时收到多条群推送而给用户”重复推送“的错觉，）
                    Vector<Vector> lastUsersUseThisIOSDeviceToken =
                            LogicProcessor2.db.queryData("select user_uid from missu_users "
                                    + "where ios_device_token='" + iosDeviceToken
                                    // 排除本次登陆的”我“自已
                                    + "' and user_uid<>" + userAuthedInfo.getUser_uid());

                    if (lastUsersUseThisIOSDeviceToken.size() > 0) {
                        LoggerFactory.getLog().debug("【iOS登陆特殊处理】已查到共" + lastUsersUseThisIOSDeviceToken.size() + "个账号"
                                + Arrays.toString(lastUsersUseThisIOSDeviceToken.toArray())
                                + " 登陆该ios设备（deviceToken=" + iosDeviceToken + "），马上设置这些账号的ios_device_token=null .......");

                        // 置null
                        LogicProcessor2.db.update("update missu_users set ios_device_token=null where ios_device_token=?"
                                , new Object[]{iosDeviceToken}, false);

                        for (Vector r : lastUsersUseThisIOSDeviceToken) {
                            String lastUserUseThisIOSDeviceToken = (String) r.get(0);
                            //** 【高速缓存】即时更新用户缓存数据（因为用户信息中的ios设备的消息推送token字段已经更新为null了）
                            UsersInfoCacheProvider.getInstance().reloadOneFromDB(lastUserUseThisIOSDeviceToken);
                        }
                    } else {
                        LoggerFactory.getLog().debug("【iOS登陆特殊处理】没有查到在此设备上登陆过的其它账号（deviceToken=" + iosDeviceToken + "），继续进行其它逻辑.");
                    }
                }
            }

            //************************************** 【2】更新登陆信息 **********************************************
            // 更新最近登陆时间、最近登陆ip、用户的ios设备id（如果是ios设备登陆的话）
            LogicProcessor2.db.update("update missu_users set latest_login_time=" + DBDepend.getDefaultDatetimeFunc() +
                            ",latest_login_time2=?, latest_login_ip=?, ios_device_token=? where user_uid=?"
                    , new Object[]{System.currentTimeMillis(), ip, iosDeviceToken, userAuthedInfo.getUser_uid()}, false);

            // 目前android登陆时才需要进行手机端版本更新检查（ios的app store自动完成、web里不需要版本更新）
            if (isAndroid) {
                //************************************** 【3】保留字段的使用 **********************************************
                // ## 【开启即生效】字段_ex3用于开启客户端的强制更新功能，开启的逻辑是当客户端的当前版本未更新到此版本时，无条件阻止客户登陆（客户端自版本42起已启用））
                userAuthedInfo.setMustVersion4A("410");// 表示要强制更新的版本号(小于此版本号的都需要强制更新)
//				userAuthedInfo.setMustVersionDesc4A("This version includes important updates, you must complete update before login!");// 将用于客户端在ui上的提示信息
                userAuthedInfo.setMustVersionDesc4A("您的版本已过时，请升级客户端后再试，感谢理解！");// 将用于客户端在ui上的提示信息
            }

            // $$ 【自20170216 RainbowChat4.0生效】目前本字段为保留字段，建议2次开发时，由开发者自行决定
            //      token的生成规则和使用逻辑。此token将用于返回客户端后连接到IM服务器时使用，此token将决
            //      定IM即时通讯通道的安全性。
//            userAuthedInfo.setToken(LogicUtils.getToken(phone));
            String tokenKey = JedisTools.getLoginUserKey(userAuthedInfo.getUser_uid());
            String token = LogicUtils.getLoginToken(phone);
            long expireTime = Long.parseLong(BaseConf.getInstance().getREDIS_TOKEN_EXPIRETIME());
            JedisTools.set(tokenKey, token, expireTime);
            userAuthedInfo.setToken(token);

            //************************************** 【4】更新当前登陆用户缓存 **********************************************
            //** 【高速缓存】即时更新用户缓存数据（因为用户信息中的ios设备的消息推送token字段可能已经更新了）
            UsersInfoCacheProvider.getInstance().reloadOneFromDB(userAuthedInfo.getUser_uid());
//            UserInfo4Cache userInfo4Cache = UsersInfoCacheProvider.getInstance().get(userAuthedInfo.getUser_uid());
//            userInfo4Cache.setToken(userAuthedInfo.getToken());
        }
        return userAuthedInfo;
    }

    /**
     * 注销登陆.
     * <p>
     * 注销登陆可在客户端：退出APP、切换APP时调用。除了iOS设备外，暂时其它客户端不强制非得进行注销登陆。
     *
     * @param ai 注销登陆信息封装对象
     * @throws Exception
     * @since 4.3
     */
    public static void authrizationLogout(LogoutInfo ao, String ip) throws Exception {
        LoggerFactory.getLog().debug("[HTTP]<注销登陆> 由uid=" + ao.getUid()
                + "[客户端类型：" + ao.getOsType() + "] 的账号发出的注销登陆请求 ...");

        // 如果是ios设备的注销登陆：则设置它的设备token为空（这样就收不到ios的离线消息推送了）
        if (ao.isIOS()) {
            if (!ao.isDontClearDeviceToken()) {
                LogicProcessor2.db.update("update missu_users set ios_device_token=null where user_uid=?"
                        , new Object[]{ao.getUid()}, false);

                //** 【高速缓存】即时更新用户缓存数据（注销的时候，该用户在db中的ios device token字段已被置空了）
                UsersInfoCacheProvider.getInstance().reloadOneFromDB(ao.getUid());
            }
        }
        // 其它客户端类型在注销登陆时需要做的事情
        else {
            // do nothing
        }

        // 加入用户操作日志
        OperateLog oprLog = new OperateLog(OperateLog.OPERATE_LOG_TYPE_LOGOUT_HTTP_LOGIN, ao.getUid(), ao.getDeviceInfo());
        LogicUtils.addOperateLog(oprLog);
    }


    /**
     * 将数据对象发回给PC客户端.
     *
     * @param res           客户端响应HttpServletResponse对象
     * @param objFromServer 要发送的可系列化的数据对象
     * @throws Exception 如果在操作OutputStream时出现异常
     */
    // 重写父类方法的目的是将父类中发出的是java序列化对象的方式改为JSON文本方式（以便跨平台（非java平台））
    @Override
    protected void sendToClient(HttpServletResponse res, DataFromServer objFromServer) throws IOException {
        String toCient = new Gson().toJson(objFromServer);
        // 发出的是JSON文本描述的DataFromClient对象
        byte[] bs = toCient.getBytes(EncodeConf.ENCODE_TO_CLIENT);//
        OutputStream out = res.getOutputStream();
        out.write(bs);
        out.flush();
        out.close();

        LoggerFactory.getLog().info("【<== 处理完成-返回JSON结果】:" + toCient);
    }

    /**
     * 读取PC客户端传过来的数据对象。
     *
     * @param req 客户端请求HttpServletRequest对象
     * @return 返回读取出来的客户端数据对象
     * @throws IOException            如果req.getInputStream()出现异常时抛出
     * @throws ClassNotFoundException 如果is.readObject()出现异常时抛出
     */
    // 重写父类方法的目的是将父类中接收的是java序列化对象的方式改为JSON文本方式（以便跨平台（非java平台））
    @Override
    protected DataFromClient recieveFromClient(HttpServletRequest req) throws IOException, ClassNotFoundException {
        InputStream is = req.getInputStream();
        int ch;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while ((ch = is.read()) != -1) {
            bos.write(ch);
        }
        is.close();
        byte[] bs = bos.toByteArray();
        bos.close();
        // 接收的数据是JSON文本描述的DataFromClient对象
        String res = SQLSave(new String(bs, EncodeConf.DECODE_FROM_CLIENT));
//		System.out.println(res);
        LoggerFactory.getLog().info("【==> 收到接口请求-数据原型】:" + res);
        DataFromClient dfc = new Gson().fromJson(res, DataFromClient.class);//JSON.parseObject(res, DataFromClient.class);

        StringBuffer sbf = new StringBuffer();
        if (dfc.getProcessorId() != -9999999) {
            sbf.append(dfc.getProcessorId());
        }
        if (dfc.getJobDispatchId() != -9999999) {
            sbf.append("#").append(dfc.getJobDispatchId());
        }
        if (dfc.getActionId() != -9999999) {
            sbf.append("#").append(dfc.getActionId());
        }
        dfc.setInfName(sbf.toString());
//		System.out.println("getActionId="+dfc.getActionId()
//				+", getJobDispatchId="+dfc.getJobDispatchId()
//				+", getProcessorId="+dfc.getProcessorId()
//				+", getNewData="+dfc.getNewData()
//				);
        return dfc;
    }


    // 打印客户端设备类型
    private static String printOS(int osType) {
        if (0 == osType) {
            return "Android";
        } else if (1 == osType) {
            return "iOS";
        } else if (2 == osType) {
            return "Web";
        } else {
            return "未知";
        }
    }

    // 打印rest接口的编号，方便调试
    public static String printRestNum(int processorId, int jobDispatchId, int actionId) {
        String ret = "";
        if (processorId > 0) {
            ret += String.valueOf(processorId);
        }
        if (jobDispatchId > 0) {
            ret += "-" + String.valueOf(jobDispatchId);
        }
        if (actionId > 0) {
            ret += "-" + String.valueOf(actionId);
        }

        return ret;
    }

    /**
     * 强制保护因程序员的后台SQL语句未使用预编译语句而发生SQL注入攻击的风险。
     *
     * @param originalStr
     * @return
     */
    public static String SQLSave(String originalStr) {
        if (originalStr != null) {
            // 此替换逻辑来自网上，实测会将整条SQL全部转空，不是最佳逻辑，20190324日已弃用。
            // 比如：此逻辑会导致web端传过来的html转义字符（如“&gt;”）被错误地整块替换掉
            //       newData字段，从而无法成功进行http rest的请求处理！
//			return originalStr.replaceAll(".*([';]+|(--)+).*", " ");

            // 将SQL注入风险关键字符替换成空格，启用于20190324日
            return originalStr.replaceAll("'", " ").replaceAll(";", " ").replaceAll("--", " ");
        }

        return originalStr;
    }

//	public static void main(String[] args)
//	{
//		String s = "select * from users where username='';DROP Database (DB Name) --' and password=''";
//		System.out.println(">>>>>>>>>>>>>>>>>>>"+MyControllerJSON.SQLSave(s));
//	}
}
